/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Eclipse Public License (EPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/eclipse-1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.mmtk.policy.compressor;

import static org.mmtk.policy.compressor.Chunk.LOG_BYTES_IN_CHUNK;

import org.mmtk.utility.Constants;
import org.mmtk.utility.Log;
import org.mmtk.utility.alloc.Allocator;
import org.mmtk.vm.VM;

import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.ObjectReference;
import org.vmmagic.unboxed.Word;

/**
 * This class defines operations over block-granularity meta-data
 *
 */
@Uninterruptible
public class Block implements Constants {
  
  private static final boolean VERBOSE = true;
  private static final boolean VERY_VERBOSE = VERBOSE && false;
  // Blocks
  //--------
  //static final int LOG_BYTES_IN_BLOCK = 9; // 512 byte blocks
  public static final int LOG_BYTES_IN_BLOCK = LOG_BYTES_IN_PAGE + 3; // Like BumpPointer
  public static final int BYTES_IN_BLOCK = 1<<LOG_BYTES_IN_BLOCK;
  public static final Word BLOCK_MASK = Word.fromIntZeroExtend(BYTES_IN_BLOCK - 1);
  
  // block <-> page
  static final int LOG_BLOCKS_IN_PAGE = LOG_BYTES_IN_PAGE - LOG_BYTES_IN_BLOCK;
  static final int BLOCKS_IN_PAGE = 1<<LOG_BLOCKS_IN_PAGE;
  
  // block <-> chunk
  //--------
  static final int LOG_BLOCKS_IN_CHUNK = LOG_BYTES_IN_CHUNK - LOG_BYTES_IN_BLOCK;
  static final int BLOCKS_IN_CHUNK = 1<< LOG_BLOCKS_IN_CHUNK; 
  //protected static final Word CHUNK_MASK = Word.fromIntZeroExtend(BYTES_IN_CHUNK - 1);
  
  public static Address align(final Address ptr) {
    return ptr.toWord().and(BLOCK_MASK.not()).toAddress();
  }

  public static boolean isAligned(final Address address) {
    return address.EQ(align(address));
  }
  
  public static Address getBlockBase(Address address) {
    return address.toWord().and(BLOCK_MASK.not()).toAddress();
  }
  
  public static Address getBlockBase(ObjectReference objRef) {
    return getBlockBase(objRef.toAddress());
  }
  
  public static Address nextBlock(Address address) {
    return getBlockBase(address).plus(BYTES_IN_BLOCK);
  }
  
  public static int liveData(Address block) {
    return liveData(block, Address.zero());
  }
  
  /**
   * Calculate live data in block up to address limit 
   * @param block
   * @param limit
   */
  public static int liveData(Address block, Address limit) {
    if(VM.VERIFY_ASSERTIONS) {
      VM.assertions._assert(Block.align(block).EQ(block));
    }
    return scanBlock(block, limit);
    
  }
  
  @Inline
  private static int scanBlock(Address start, Address limit) {
    int totalLiveData = 0;
    Address blockEnd;
    if (limit.isZero()) {
      blockEnd = start.plus(BYTES_IN_BLOCK);
    }
    else {
      blockEnd = limit;
    }
    
    Address current = start;

    do {
      Address currentLiveAddr = CompressorMetadata.getLiveWordAddress(current);
      
      // Find first live word != 0 in live map
      while(currentLiveAddr.loadWord().isZero() && current.LT(blockEnd)) {
        current = current.plus(1<<(CompressorMetadata.LOG_LIVE_COVERAGE));
        currentLiveAddr = CompressorMetadata.getLiveWordAddress(current);
      }
      
      // Find the live object from that live word
      while(!CompressorMetadata.liveBitSet(current) && current.LT(blockEnd)) {
        current = current.plus(BYTES_IN_WORD);
      }
      
      // Ensure we are inside limits
      if (current.GE(blockEnd)) {
        break;
      }
     
      ObjectReference currentRef = VM.objectModel.getObjectFromStartAddress(current);
      if (VERY_VERBOSE) {
        VM.objectModel.dumpObject(currentRef);
      }
      
      // Read end address first, as scan may be destructive
      Address currentObjectEnd = VM.objectModel.getObjectEndAddress(currentRef);
      
      int size = VM.objectModel.getSizeWhenCopied(currentRef);
      int align = VM.objectModel.getAlignWhenCopied(currentRef);
      int offset = VM.objectModel.getAlignOffsetWhenCopied(currentRef);
      
      totalLiveData = Allocator.alignAllocationNoFill(Address.zero().plus(totalLiveData), 
          align, offset).toInt();
      
      totalLiveData += size;
      
      if (currentObjectEnd.GE(blockEnd)) {
        /* We have scanned the last object */
        break;
      }
      
      current = currentObjectEnd;
    } while (true);
    
    return totalLiveData;
  }
  
}

//
//@Inline
//private static int scanBlock(Address start, Address limit) {
//  int totalLiveData = 0;
//  Address blockEnd;
//  if (limit.isZero()) {
//    blockEnd = start.plus(BYTES_IN_BLOCK);
//  }
//  else {
//    blockEnd = limit;
//  }
//  
//  Address current = start;
//
//  do {
//    Address currentLiveAddr = CompressorMetadata.getLiveWordAddress(current);
//    
//    // Find first live word != 0 in live map
//    while(currentLiveAddr.loadWord().isZero() && current.LT(blockEnd)) {
//      current = current.plus(1<<(CompressorMetadata.LOG_LIVE_COVERAGE));
//      currentLiveAddr = CompressorMetadata.getLiveWordAddress(current);
//    }
//    
//    // Find the live object from that live word
//    while(!CompressorMetadata.liveBitSet(current) && current.LT(blockEnd)) {
//      current = current.plus(BYTES_IN_WORD);
//    }
//    
//    // Ensure we are inside limits
//    if (current.GE(blockEnd)) {
//      break;
//    }
//   
//    ObjectReference currentRef = VM.objectModel.getObjectFromStartAddress(current);
//    if (VERBOSE) {
//      VM.objectModel.dumpObject(currentRef);
//    }
//    
//    // Read end address first, as scan may be destructive
//    Address currentObjectEnd = VM.objectModel.getObjectEndAddress(currentRef);
//    
//    if (VERBOSE) {
//      //Log.write(currentObjectEnd.diff(currentRef.toAddress()).toInt()); Log.writeln();
//      //Log.write(VM.objectModel.getCurrentSize(currentRef)); Log.writeln();
//      // XXX: TODO: these must be equal or which one to use ?
//    }
//
////    int size = VM.objectModel.getSizeWhenCopied(currentRef);
////    int align = VM.objectModel.getAlignWhenCopied(currentRef);
////    int offset = VM.objectModel.getAlignOffsetWhenCopied(currentRef);
////    Allocator.alignAllocationNoFill(toCursor.get(), align, offset)
//    
//    
//    totalLiveData += currentObjectEnd.diff(currentRef.toAddress()).toInt();
//    
//    if (currentObjectEnd.GE(blockEnd)) {
//      /* We have scanned the last object */
//      break;
//    }
//    
//    current = currentObjectEnd;
//  } while (true);
//  
////  if (VERBOSE) {
////    Log.write("totalLiveData["); 
////    Log.write(start); Log.write(","); Log.write(blockEnd);
////    Log.write("]"); 
////    Log.write("= "); Log.write(totalLiveData); Log.writeln();
////  }
//  return totalLiveData;
//}

